package com.bolt.support.spring.jpa.jpql;


import com.bolt.common.utils.StrUtil;
import com.bolt.support.query.conditions.domain.SqlClause;

import java.util.EnumMap;
import java.util.List;
import java.util.Map;

public class JPQLBuilder {

    private static final String COMMA = ", ";

    private static final String DELETE = "delete from ";

    private static final String FROM = " from ";

    private static final String GROUP_BY = " group by ";

    private static final String HAVING = " having ";

    private static final String ORDER_BY = "\norder by ";

    private static final String SELECT = "select ";


    private static final String SELECT_COUNT = "select count(1)\n";

    private static final String SELECT_COUNT_DISTINCT = "select count(distinct ";

    private static final String SELECT_DISTINCT = "select distinct ";

    private static final String SELECT_SUB_COUNT = "select count(1) from ({})  a ";

    private static final String SET = "\nset ";

    private static final String UPDATE = "update ";

    private static final String WHERE = "\nwhere ";

    private static final String WITH = " with ";

    private static final String ON = " on ";


    private static final Map<JoinType, String> joinTypes = new EnumMap<JoinType, String>(JoinType.class);

    static {
        joinTypes.put(JoinType.DEFAULT, COMMA);
        joinTypes.put(JoinType.FULLJOIN, "\n  full join ");
        joinTypes.put(JoinType.INNERJOIN, "\n  inner join ");
        joinTypes.put(JoinType.JOIN, "\n  inner join ");
        joinTypes.put(JoinType.LEFTJOIN, "\n  left join ");
        joinTypes.put(JoinType.RIGHTJOIN, "\n  right join ");
    }

    private final StringBuilder builder = new StringBuilder(128);

    private void append(final String str) {
        builder.append(str);
    }

    private void handle(final SqlClause<?> clause, boolean addAlias) {
        append((String) clause.render(addAlias));
    }

    private void handle(final String sep, final List<? extends SqlClause<?>> clauses) {
        for (int i = 0; i < clauses.size(); i++) {
            if (i != 0) {
                append(sep);
            }
            handle(clauses.get(i), true);
        }
    }

    private void handle(JoinClause joinClause) {
        String query_tmp = joinClause.getOpMixin().render(false);
        String alias = joinClause.getTarget().getAlias();
        if (StrUtil.isNotBlank(alias)) {
            String owned = joinClause.getTarget().getOwned();
            String pattern = "(" + owned + "\\.)";
            String newAlias = alias + ".";
            query_tmp = query_tmp.replaceAll(pattern, newAlias);
        }
        append(query_tmp);
    }

    private void buildJoins(List<JoinClause> joinClauses) {
        for (int i = 0; i < joinClauses.size(); i++) {
            final JoinClause joinClause = joinClauses.get(i);
            if (i > 0) {
                append(joinTypes.get(joinClause.getType()));
            }
            handle(joinClause.getTarget(), true);
            if (null != joinClause.getOpMixin()) {
                append(ON);
                handle(joinClause);
            }
        }
    }


    public String renderDelete(QueryMetadata metadata) {
        append(DELETE);
        handle(metadata.getJoins().get(0).getTarget(), false);
        if (metadata.getWhere() != null) {
            append(WHERE);
            handle(metadata.getWhere(), false);
        }
        return builder.toString();
    }

    public String renderUpdate(QueryMetadata metadata) {
        final List<SqlClause<?>> updates = metadata.getUpdates();
        final OperationClause where = metadata.getWhere();
        append(UPDATE);
        handle(metadata.getJoins().get(0).getTarget(), false);
        append(SET);

        for (int i = 0; i < updates.size(); i++) {
            SqlClause<?> clause = updates.get(i);
            if (i > 0) {
                append(COMMA);
            }
            if (clause instanceof StitchClause) {
                handle(clause, true);
                continue;
            } else if (clause instanceof ColumnClause) {
                i = i + 1;
                handle(clause, true);
                append(" = ");
                handle(updates.get(i), true);
            } else {
                continue;
            }
        }
        if (where != null) {
            append(WHERE);
            handle(where, false);
        }
        return builder.toString();
    }

    public String renderQuery(QueryMetadata metadata, boolean forCount) {
        List<SqlClause<?>> select = metadata.getSelect();
        final List<JoinClause> joins = metadata.getJoins();
        final OperationClause where = metadata.getWhere();
        final List<SqlClause<?>> groupBy = metadata.getGroupBy();
        final List<SqlClause<?>> orderBy = metadata.getOrderBy();
        final OperationClause having = metadata.getHaving();


        if (forCount && !metadata.isSubCount()) {
            if (!metadata.isDistinct()) {
                append(SELECT_COUNT);
            } else {
                append(SELECT_COUNT_DISTINCT);
                if (!joins.isEmpty()) {
                    append(joins.get(0).getTarget().getOwned());
                } else {
                    handle(COMMA, select);
                }
                append(")\n");
            }
        } else {
            if (!select.isEmpty()) {
                if (!metadata.isDistinct()) {
                    append(SELECT);
                } else {
                    append(SELECT_DISTINCT);
                }
                handle(COMMA, select);
            }
        }
        if (!joins.isEmpty()) {
            append(FROM);
            buildJoins(joins);
        }
        if (null != where) {
            append(WHERE);
            handle(where, false);
        }
        if (!groupBy.isEmpty() && (!forCount || metadata.isSubCount())) {
            append(GROUP_BY);
            handle(COMMA, groupBy);
        }
        if (null != having && !forCount) {
            append(HAVING);
            handle(having, false);
        }
        if (!orderBy.isEmpty() && !forCount) {
            append(ORDER_BY);
            handle(COMMA, orderBy);
        }

        if (forCount && metadata.isSubCount()) {
            String nativeSql = metadata.nativeTranslator().translate(builder.toString()) ;
            return StrUtil.format(SELECT_SUB_COUNT, nativeSql);
        }
        return builder.toString();
    }

}

 